The way we write JavaScript can always be improved.
In this article, we’ll look at how to improve our code styles by improving the basic syntax we use in our code.
Naming Conventions
The names we use for identifiers like function names, class names, variable names, etc. should follow commonly accepted conventions.
They’re accepted because they make our code clear for most people.
To write good names, we should avoid single-letter names. We should be descriptive with our naming.
For instance, the following is bad:
let x;
because no one knows what x
means.
However, the following is good:
let numFruits;
because we know from the variable name that it’ll store the number of fruits.
We also know that it’s a number, which is even better since JavaScript doesn’t have any data type annotations built-in to identify the type of a variable.
Likewise, the following is a bad way to name a function:
const q = () => {};
but the following is good:
const countFruits = () => {};
With the function name above, we actually know what the function actually does.
Notice that the good naming examples all use camelCase. This is the way to name most things, with the exception of class and constructor names.
For class and constructor function names, we should use PascalCase to name them.
For instance, the following is a good example of a class name:
class Fruit {}
for constructor functions, we should name it as follows:
function Fruit(){}
Semicolons
We should always add semicolons at the end of a new line. Because if we don’t the JavaScript interpreter will automatically add them to any place that it sees fit.
For instance, if we have something like:
const foo = ()=> {
return
'foo'
}
Then when we call foo
, it’ll return undefined
because the function ended after the return
keyword. The 'foo'
string isn’t considered to be part of the return statement.
Therefore, we should always add semicolons as follows:
const foo = () => {
return 'foo';
}
Now the JavaScript Interpret knows that return 'foo';
is actually one line.
It also makes human reading it understand it better as well as it removed any ambiguity.
Comments
If we need comments, we need to standardize our comment schemes so that they’re consistent.
For a block comment, we write the following:
/* comment */
To make documentation easy, we should use JSDoc comments so that we can use them to create documentation for us automatically without doing extra manual work:
/**
* Represents a fruit.
* @constructor
* @param {string} name - The name of a fruit.
* @param {string} color - The color of a fruit.
*/
function Fruit(name, color) {
this.name = name;
this.color = color;
}
The code above has a big block comment with the parameters of the Fruit
constructor function with the name
and color
parameters.
In the comment, we annotate the data type of the parameters so that it’s clear to everyone reading the code comment on what the function accepts.
Since JavaScript has no way to distinguish the data type of the parameters from within the code, the type annotation in the comments is useful.
The comment above is standard a JSDoc style comment.
Promises
If we deal with asynchronous code that deals with callbacks, then we should wrap them in a promise so that we can chain them easily.
For instance, if we need to run code with a delay, we should write a function that returns a promise to do that:
const delay = (ms) => {
return new Promise((resolve) => {
setTimeout(() => {
console.log('delay');
resolve();
}, ms)
})
}
In the code above, we have a delay
function that returns a promise that runs the serTimeout
function inside it to run some code.
Then within the setTimeout
callback, we call resolve
to fulfill the promise.
This way, we won’t get callback hell by nesting async callbacks many levels deep.
To handle errors, we can write the following code:
const fs = require('fs');
const readFile = (filename) => {
return new Promise((resolve, reject) => {
fs.readFile(filename,(err, data) => {
if (err) {
reject(err);
}
resolve(data);
});
})
}
(async () => {
const data = await readFile('foo.txt');
console.log(data.toString())
})();
In the code above, we have the readFile
function, which returns a promise that runs the fs.readFile
function. Inside the readFile
‘s callback, if err
is defined, then we call reject
to reject the promise. We can resolve the promise if data
is provided.
Then we can use async
and await
to use the promise as we did in the last few lines of code.
Conclusion
Names and comments should be consistent. Names should be self-documenting with descriptive names and in the case that is commonly accepted.
Also, we should wrap async code that aren’t promises with promises so that we can chain them.